package com.vx.xmlModelToData.convertor;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.xml.XMLSerializer;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import com.vx.xmlModelToData.cache.ModelCache;
import com.vx.xmlModelToData.util.JsonUtil;

public class ModelXML {

	private Document document=null;
	private Document dataDoc=null;
	
	//根据是否是转json的操作，来判断list只有一个对象时，是否再添加一个空的对象用于转json使用。
	private static boolean isToJson=false;
	/**
	 * 是否保留空的节点
	 */
	private boolean isIncludeNull=false;
	
	/**
	 * 数组数据是否组成成一个大的节点对象
	 */
	private boolean isCombineChildList=true;
	
	/**
	 * 创建数据的Document对象，默认xml不输出没有值的空节点
	 * @param modelFile 模版文件
	 * @throws DocumentException
	 */
	public ModelXML(String modelFile) throws DocumentException{
			document=ModelCache.getMode(modelFile);
	}
	
	/**
	 * 创建数据的Document对象，设置是否输出没有值的空节点
	 * @param modelFile 模版文件
	 * @param iscludeNullNode 是否保留空节点
	 * @throws DocumentException
	 */
	public ModelXML(String modelFile,boolean iscludeNullNode) throws DocumentException{
		document=ModelCache.getMode(modelFile);
		isIncludeNull=iscludeNullNode;
}
	
	/**
	 * 单个Map里面的数据转换成XML
	 * @param data  数据value为七种基本数据格式和string类型
	 * @return
	 */
	public String getDataXML(Map<String,Object> data){
		copyRoot();
		//直接存放root，Document会报错，如果root负责一份副本，Document存放副本就OK。
//		Element elementClone= (Element) root.clone();
//		dataDoc.setRootElement(elementClone);
		compositionChild(data,document.getRootElement(),dataDoc.getRootElement());
		return dataDoc.asXML();
	}

	
	/**
	 * 多个map转成xml
	 * <p>
	 * Map<String,Map<String,Object>> 中的外部Map的key为模版中的二级节点name,内部Map数据的value为七种基本数据格式和string类型
	 * 例如：
	 * <code>
	 * <root>
	 * 		<secondname>
	 * 				<id></id>
	 * 				<type></type>
	 * 		</secondname>
	 * </root>
	 * </code>
	 * </p>
	 * @param dataMap  
	 * @return
	 */
	public String getDataXMLList(Map<String,Map<String,Object>> dataMap){
		copyRoot();
		for(String key : dataMap.keySet()){
			compositionChild(dataMap.get(key),(Element)document.getRootElement().element(key).clone(),dataDoc.getRootElement());
		}
		return dataDoc.asXML();
	}
	
	/**
	 * 循环element节点集合，在有对应关系的节点中添加数据
	 * @param map
	 * @param element
	 */
	private void compositionChild(Map<String, Object> dataMap, Element element,Element parentElement) {
		String modelValue=null;
		String val=null;
		for (Iterator iterator = element.elementIterator(); iterator.hasNext();) {
			Element el = (Element) ((Element) iterator.next()).clone();
			modelValue=el.getText();
			if(StringUtils.isNotEmpty(modelValue)){
				val=String.valueOf(dataMap.get(modelValue));
				el.setText(val);
				parentElement.add(el);
			}
			if(isIncludeNull&&StringUtils.isEmpty(val)){
				parentElement.add(el);
			}
		}
	}

	/**
	 * 根据模版创建数据xml的根节点
	 * @return
	 */
	private void copyRoot() {
		Element root=(Element)document.getRootElement().clone();
		root.clearContent();
//		root.add(document.getRootElement().getNamespace());
		dataDoc = DocumentHelper.createDocument(root);
	}
	
	/**
	 * java数据对象转换成xml
	 * <p>
	 * bean中的属性名称需要与xml模版中的节点text一致<br>
	 * 如果属性类型为引用数据类型，则属性名需要与节点名称一致。
	 * </p>
	 * @param beanObject
	 * @return
	 * @throws Exception
	 * 
	 */
	public String getBeanXmlFromModelXml(Object beanObject) throws Exception{
		copyRoot();
		JSONObject json=JsonUtil.beanToJSON(beanObject);
		addDataElementByModelJSON(json,(Element)document.getRootElement().clone(),dataDoc.getRootElement());
		return dataDoc.asXML();
	}
	
	/**
	 * java数据对象转换成json
	 * <p>
	 * bean中的属性名称需要与xml模版中的节点text一致<br>
	 * 如果属性类型为引用数据类型，则属性名需要与节点名称一致。
	 * </p>
	 * @param beanObject
	 * @return
	 * @throws Exception
	 */
	public String getBeanJsonFromModelXml(Object beanObject)throws Exception{
		isToJson=true;
		String jsonStr=Xml2Json(getBeanXmlFromModelXml(beanObject));
		isToJson=false;
		return jsonStr;
	}
	
	/**
	 * 将Bean转换成xml。 因className.getDeclaredField(nodeText) 不能回去私有属性。 此方法废弃
	 * @param obj
	 * @param className
	 * @param modelElement
	 * @param dataElement
	 * @throws Exception
	 */
	@Deprecated
 	public String getDataXML(Object obj,Class className) throws Exception{
		copyRoot();
		addDataElementByModel(obj,className,(Element)document.getRootElement().clone(),dataDoc.getRootElement());
		
		addDataElementByModelSZ(obj,className,document.getRootElement(),dataDoc.getRootElement());
		
		return null;
	}
	
	private void addDataElementByModelSZ(Object obj, Class className,Element clone, Element rootElement) throws Exception{
		Field[] fields=className.getDeclaredFields();
		List<Field> fieldList=new ArrayList<Field>();
		Collections.addAll(fieldList, fields);
		for (int i = 0; i < fields.length; i++) {
			Object fieldValue=null;
			Field field=fields[i];
			//获取私有属性的信息，需要对属性设置不进行代码检查
			field.setAccessible(true);
			String typeName=field.getType().getName();
			String fieldName=field.getName();
			
			if(typeName.equals("java.util.List")){
				//获取泛型类中的泛型具体对应的类
				Type genericType=field.getGenericType();
				Type[] types=((ParameterizedType)genericType).getActualTypeArguments();
				Class genericClass=(Class) types[0];
				
				List valList=(List) field.get(obj);
//				Method method=className.getMethod("get"+getMethodName(fieldName));
//				List valList=(List) method.invoke(obj);
			}else{
				fieldValue=field.get(obj);
			}
		}
	}

	/**
	 * 将json对象根据模版配置转换成xml格式
	 * @param json
	 * @param modelElement 模版节点
	 * @param dataElement 需要添加数据的父节点
	 * @throws Exception
	 */
	private void addDataElementByModelJSON(JSONObject json,Element modelElement, Element dataElement) throws Exception {
		for(Iterator it=modelElement.elementIterator();it.hasNext();){
			Element node=(Element)((Element) it.next()).clone();
			if(node.elements().size()==0){
				String nodeText=node.getText();
				if(StringUtils.isEmpty(nodeText) && isIncludeNull){
					node.setText(String.valueOf(""));
					dataElement.add(node);
				}else if(StringUtils.isNotEmpty(nodeText)){
					Object value=json.get(nodeText);
					node.setText(String.valueOf(value));
					dataElement.add(node);
				}
			}else{
				String nodeName=node.getName();
				//根据节点名称查询对象中是否有对应的属性，
				Object obj=json.get(nodeName);
				if(obj==null){
					//如果没找到对应的属性，但这个节点下面还有子节点， 就判断是否保留这个节点。
					//例如 节点files 但没有files对象，此时判断是否还需要files节点
					if(isCombineChildList){
					Element childRoots=(Element) node.clone();
					childRoots.clearContent();
					dataElement.add(childRoots);
					addDataElementByModelJSON(json,node,childRoots);
					}else{
						addDataElementByModelJSON(json,node,dataElement);
					}
				}else if(obj instanceof JSONArray){
					JSONArray array=(JSONArray) obj;
					if(isToJson&&array.size()==1){
						dataElement.addElement(nodeName);
					}
					for(int k=0;k<array.size();k++){
						JSONObject child=array.getJSONObject(k);
						Element dataNode=(Element) node.clone();
						dataNode.clearContent();
						dataElement.add(dataNode);
						addDataElementByModelJSON(child,node,dataNode);
					}
				}else if(obj instanceof JSONObject){
					JSONObject childJson=(JSONObject) obj;
					Element dataNode=(Element) node.clone();
					dataNode.clearContent();
					dataElement.add(dataNode);
					addDataElementByModelJSON(childJson,node,dataNode);
				}
			}
		}
	}
	
	
	/**
	 * 将Bean转换成xml。 因className.getDeclaredField(nodeText) 不能获取私有属性信息。 此方法废弃
	 * @param obj
	 * @param className
	 * @param modelElement
	 * @param dataElement
	 * @throws Exception
	 */
	@Deprecated
	private void addDataElementByModel(Object obj, Class className,Element modelElement, Element dataElement) throws Exception {
		for(Iterator it=modelElement.elementIterator();it.hasNext();){
			Element node=(Element) it.next();
			if(node.elements().size()==0){
				String nodeText=node.getText();
				if(StringUtils.isEmpty(nodeText)&&!isIncludeNull){
					node.getParent().remove(node);
				}else{
					/* 
					 作废原因：因没有设置field的 field.setAccessible(true); 所有此处拿不到私有的field对象。 
					*/
					Field field=className.getDeclaredField(nodeText);
					String typeName=field.getType().getName();
					Object fieldValue=null;
					if(typeName.equals("boolean")||typeName.equals("java.lang.Boolean")){
						Method method=className.getMethod("is"+getMethodName(nodeText));
						fieldValue=method.invoke(obj);
					}else{
						fieldValue=field.get(obj);
					}
					node.setText(String.valueOf(fieldValue));
					dataElement.add(node);
				}
			}else{
				String nodeName=node.getName();
				Field field=className.getDeclaredField(nodeName);
				Type genericType=field.getGenericType();
				Type[] types=((ParameterizedType)genericType).getActualTypeArguments();
				Class genericClass=(Class) types[0];
				Method method=className.getMethod("get"+getMethodName(nodeName));
				List valList=(List) method.invoke(obj);
				Element childElement=dataElement.addElement(nodeName+"s");
				for (Object object : valList) {
					addDataElementByModel(object,genericClass,(Element)node.clone(),childElement);
				}
			}
		}
	}

	/**
	 * xml转json，不输出命名空间信息
	 * @param xmlData
	 * @return
	 */
	public static String Xml2Json(String xmlData) {
		XMLSerializer xmlSerializer=new XMLSerializer();
		xmlSerializer.setSkipNamespaces(true);
		JSON json=xmlSerializer.read(xmlData);
		String jsonStr=json.toString();
		jsonStr=StringUtils.replace(jsonStr,",[]]}","]}");
		jsonStr=StringUtils.replace(jsonStr,":null",":\"\"");
		jsonStr=StringUtils.replace(jsonStr,"[null,","[");
		jsonStr=StringUtils.replace(jsonStr,":[]",":\"\"");
		
		return jsonStr;
	}
	
	
	
	  public void setCombineChildList(boolean isCombineChildList) {
		this.isCombineChildList = isCombineChildList;
	}

	/**
	   * 获取方法名 
	   * <p>
	   * 把一个字符串的第一个字母大写、效率是最高的
	   * </p>
	   * @param fildeName
	   * @return
	   * @throws Exception
	   */
    private static String getMethodName(String fildeName) throws Exception{  
        byte[] items = fildeName.getBytes();  
        items[0] = (byte) ((char) items[0] - 'a' + 'A');  
        return new String(items);  
    } 
}
